# -*- coding: binary -*-
module Msf
module Ui
module Console
module CommandDispatcher

###
#
# Exploit module command dispatcher.
#
###
class Exploit

  include Msf::Ui::Console::ModuleCommandDispatcher
  include Msf::Ui::Console::ModuleArgumentParsing
  include Msf::Ui::Console::ModuleOptionTabCompletion

  #
  # Returns the hash of exploit module specific commands.
  #
  def commands
    super.update({
      "exploit"  => "Launch an exploit attempt",
      "rcheck"   => "Reloads the module and checks if the target is vulnerable",
      "rexploit" => "Reloads the module and launches an exploit attempt",
      "run"      => "Alias for exploit",
      "recheck"  => "Alias for rcheck",
      "rerun"    => "Alias for rexploit",
      "reload"   => "Just reloads the module"
    })
  end

  #
  # Returns the name of the command dispatcher.
  #
  def name
    "Exploit"
  end

  #
  # Launches an exploitation single attempt.
  #
  def exploit_single(mod, opts)
    begin
      session = mod.exploit_simple(opts)
    rescue ::Interrupt
      raise $!
    rescue ::Msf::OptionValidateError => e
     ::Msf::Ui::Formatter::OptionValidateError.print_error(mod, e)
    rescue ::Exception => e
      print_error("Exploit exception (#{mod.refname}): #{e.class} #{e}")
      if e.class.to_s != 'Msf::OptionValidateError'
        print_error("Call stack:")
        e.backtrace.each do |line|
          break if line =~ /lib.msf.base.simple/
          print_error("  #{line}")
        end
      end
    end

    return session
  end

  #
  # Tab completion for the run command
  #
  def cmd_run_tabs(str, words)
    fmt = {
        '-e' => [ framework.encoders.module_refnames                ],
        '-f' => [ nil                                               ],
        '-h' => [ nil                                               ],
        '-j' => [ nil                                               ],
        '-J' => [ nil                                               ],
        '-n' => [ framework.nops.module_refnames                    ],
        '-o' => [ true                                              ],
        '-p' => [ framework.payloads.module_refnames                ],
        '-r' => [ nil                                               ],
        '-t' => [ true                                              ],
        '-z' => [ nil                                               ]
    }
    flags = tab_complete_generic(fmt, str, words)
    options = tab_complete_option(active_module, str, words)
    flags + options
  end

  #
  # Tab completion for the exploit command
  #
  alias cmd_exploit_tabs cmd_run_tabs

  #
  # Launches exploitation attempts.
  #
  def cmd_exploit(*args, opts: {})
    if (args.include?('-r') || args.include?('--reload-libs')) && !opts[:previously_reloaded]
      driver.run_single('reload_lib -a')
    end

    return false unless (args = parse_exploit_opts(args))

    any_session = false
    force = args[:force] || false

    minrank = RankingName.invert[framework.datastore['MinimumRank']] || 0
    if minrank > mod.rank
      if force
        print_status("Forcing #{mod.refname} to run despite MinimumRank '#{framework.datastore['MinimumRank']}'")
        ilog("Forcing #{mod.refname} to run despite MinimumRank '#{framework.datastore['MinimumRank']}'", 'core')
      else
        print_error("This exploit is below the minimum rank, '#{framework.datastore['MinimumRank']}'.")
        print_error("If you really want to run it, do 'exploit -f' or")
        print_error("setg MinimumRank to something lower ('manual' is")
        print_error("the lowest and would allow running all exploits).")
        return
      end
    end

    mod_with_opts = mod.replicant
    mod_with_opts.datastore.import_options_from_hash(args[:datastore_options])
    rhosts = mod_with_opts.datastore['RHOSTS']
    has_rhosts_option = mod.options.include?('RHOSTS') ||
      mod.options.include?('RHOST') ||
      mod.options.include?('rhost') ||
      mod.options.include?('rhosts')

    opts = {
      'Encoder'     => args[:encoder] || mod_with_opts.datastore['ENCODER'],
      'Payload'     => args[:payload] || mod_with_opts.datastore['PAYLOAD'],
      'Target'      => args[:target] || mod_with_opts.datastore['TARGET'],
      'Nop'         => args[:nop] || mod_with_opts.datastore['NOP'],
      'LocalInput'  => driver.input,
      'LocalOutput' => driver.output,
      'RunAsJob'    => args[:jobify] || mod_with_opts.passive?,
      'Background'  => args[:background] || false,
      'Force'       => force,
      'Quiet'       => args[:quiet] || false
    }

    begin
      mod_with_opts.validate
    rescue ::Msf::OptionValidateError => e
      ::Msf::Ui::Formatter::OptionValidateError.print_error(mod_with_opts, e)
      return false
    end

    driver.run_single('reload_lib -a') if args[:reload_libs]

    if rhosts && has_rhosts_option
      rhosts_walker = Msf::RhostsWalker.new(rhosts, mod_with_opts.datastore)
      rhosts_walker_count = rhosts_walker.count
      rhosts_walker = rhosts_walker.to_enum
    end

    # For multiple targets exploit attempts.
    if rhosts_walker && rhosts_walker_count > 1
      opts[:multi] = true
      rhosts_walker.with_index do |datastore, index|
        nmod = mod_with_opts.replicant
        nmod.datastore.merge!(datastore)
        # If rhost is the last target, let exploit handler stop.
        is_last_target = (index + 1) == rhosts_walker_count
        opts["multi"] = false if is_last_target
        # Catch the interrupt exception to stop the whole module during exploit
        begin
          print_status("Exploiting target #{datastore['RHOSTS']}")
          session = exploit_single(nmod, opts)
        rescue ::Interrupt
          print_status("Stopping exploiting current target #{datastore['RHOSTS']}...")
          print_status("Control-C again to force quit exploiting all targets.")
          begin
            Rex.sleep(1)
          rescue ::Interrupt
            raise $!
          end
        end
        # If we were given a session, report it.
        if session
          print_status("Session #{session.sid} created in the background.")
          any_session = true
        end
      end
    # For single target or no rhosts option.
    else
      nmod = mod_with_opts.replicant
      if rhosts_walker && rhosts_walker_count == 1
        nmod.datastore.merge!(rhosts_walker.next)
      end
      session = exploit_single(nmod, opts)
      # If we were given a session, let's see what we can do with it
      if session
        any_session = true
        if !opts['Background'] && session.interactive?
          # If we aren't told to run in the background and the session can be
          # interacted with, start interacting with it by issuing the session
          # interaction command.
          print_line

          driver.run_single("sessions -q -i #{session.sid}")
        # Otherwise, log that we created a session
        else
          # Otherwise, log that we created a session
          print_status("Session #{session.sid} created in the background.")
        end

      elsif opts['RunAsJob'] && nmod.job_id
        # Indicate if he exploit as a job, indicate such so the user doesn't
        # wonder what's up.
        print_status("Exploit running as background job #{nmod.job_id}.")
        # Worst case, the exploit ran but we got no session, bummer.
      end
    end

    # If we didn't get any session and exploit ended launch.
    unless any_session
    # If we didn't run a payload handler for this exploit it doesn't
    # make sense to complain to the user that we didn't get a session
      unless mod_with_opts.datastore["DisablePayloadHandler"]
        fail_msg = 'Exploit completed, but no session was created.'
        print_status(fail_msg)
        begin
          framework.events.on_session_fail(fail_msg)
        rescue ::Exception => e
          wlog("Exception in on_session_open event handler: #{e.class}: #{e}")
          wlog("Call Stack\n#{e.backtrace.join("\n")}")
        end
      end
    end
  end

  alias cmd_run cmd_exploit

  def cmd_exploit_help
    print_module_run_or_check_usage(command: :run, options: @@exploit_opts)
  end

  alias cmd_run_help cmd_exploit_help

  #
  # Reloads an exploit module and checks the target to see if it's
  # vulnerable.
  #
  def cmd_rcheck(*args)
    opts = {}
    if args.include?('-r') || args.include?('--reload-libs')
      driver.run_single('reload_lib -a')
      opts[:previously_reloaded] = true
    end

    reload()

    cmd_check(*args, opts: opts)
  end

  alias cmd_recheck cmd_rcheck

  #
  # Reloads an exploit module and launches an exploit.
  #
  def cmd_rexploit(*args)
    opts = {}
    if args.include?('-r') || args.include?('--reload-libs')
      driver.run_single('reload_lib -a')
      opts[:previously_reloaded] = true
    end

    return cmd_rexploit_help if args.include?('-h') || args.include?('--help')

    # Stop existing job and reload the module
    if reload(true)
      # Delegate to the exploit command unless the reload failed
      cmd_exploit(*args, opts: opts)
    end
  end

  alias cmd_rerun cmd_rexploit
  alias cmd_rerun_tabs cmd_run_tabs
  alias cmd_rexploit_tabs cmd_exploit_tabs

  def cmd_rexploit_help
    print_module_run_or_check_usage(
      command: :rexploit,
      description: 'Reloads a module, stopping any associated job, and launches an exploitation attempt.',
      options: @@exploit_opts
    )
  end

  alias cmd_rerun_help cmd_rexploit_help

  # Select a reasonable default payload and minimally configure it
  # @param [Msf::Module] mod
  def self.choose_payload(mod)
    Msf::Payload.choose_payload(mod)
  end

end

end end end end
